/*!	 widget_keyframe_list.cpp
**	 A custom widget to manage keyframes in the timeline.
**
**	Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley
**	Copyright (c) 2007 Chris Moore
**	Copyright (c) 2009 Carlos López
**	Copyright (c) 2012-2013 Konstantin Dmitriev
**
**	This package is free software; you can redistribute it and/or
**	modify it under the terms of the GNU General Public License as
**	published by the Free Software Foundation; either version 2 of
**	the License, or (at your option) any later version.
**
**	This package is distributed in the hope that it will be useful,
**	but WITHOUT ANY WARRANTY; without even the implied warranty of
**	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
**	General Public License for more details.
**
*/

#ifdef USING_PCH
#	include "pch.h"
#else
#ifdef HAVE_CONFIG_H
#	include <config.h>
#endif

#include <synfig/general.h>

#include "widget_keyframe_list.h"
#include "app.h"
#include <gtkmm/menu.h>
#include <synfig/exception.h>
#include <ETL/misc>

#include <gui/localization.h>

#endif

using namespace std;
using namespace etl;
using namespace synfig;
using namespace studio;

#define WIDGET_KEYFRAME_LIST_DEFAULT_FPS 24.0

Widget_Keyframe_List::Widget_Keyframe_List():
    adj_default(Gtk::Adjustment::create(0, 0, 2, 1 / WIDGET_KEYFRAME_LIST_DEFAULT_FPS, 10 / WIDGET_KEYFRAME_LIST_DEFAULT_FPS)),
    kf_list_(&default_kf_list_),
    time_ratio("4f", WIDGET_KEYFRAME_LIST_DEFAULT_FPS)
{
    editable_ = true;
    fps = WIDGET_KEYFRAME_LIST_DEFAULT_FPS;
    set_size_request(-1, 64);
    // The widget respond to mouse button press and release and to
    // left button motion
    add_events(Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK);
    add_events(Gdk::BUTTON1_MOTION_MASK /*| Gdk::BUTTON3_MOTION_MASK*/);
    add_events(Gdk::POINTER_MOTION_MASK);
    set_time_adjustment(adj_default);
    queue_draw();

    // Create the window of the moving tooltip
    moving_tooltip_ = new Gtk::Window(Gtk::WINDOW_POPUP);
    moving_tooltip_->set_resizable(false);
    moving_tooltip_->set_name("gtk-tooltips");
    moving_tooltip_->set_border_width(4);
    moving_tooltip_->set_default_size(10, 10);
    moving_tooltip_->set_type_hint(Gdk::WINDOW_TYPE_HINT_TOOLTIP);

    moving_tooltip_label_ = Gtk::manage(new Gtk::Label());
    moving_tooltip_label_->set_alignment(0.5, 0.5);
    moving_tooltip_label_->show();

    moving_tooltip_->add(*moving_tooltip_label_);
}

Widget_Keyframe_List::~Widget_Keyframe_List()
{
    delete moving_tooltip_;
}

void
Widget_Keyframe_List::draw_arrow(
    const Cairo::RefPtr<Cairo::Context> &cr,
    double x, double y,
    double width, double height,
    bool fill,
    const synfig::Color &color)
{
    cr->set_source_rgba(color.get_r(), color.get_g(), color.get_b(), color.get_a());
    cr->set_line_width(1.0);
    cr->move_to(x, y);
    cr->line_to(x - 0.5 * width, y - height);
    cr->line_to(x + 0.5 * width, y - height);
    cr->close_path();

    if (fill) {
        cr->fill();
    } else {
        cr->stroke();
    }
}

bool
Widget_Keyframe_List::on_draw(const Cairo::RefPtr<Cairo::Context> &cr)
{
    // Check if the window we want draw is ready
    Glib::RefPtr<Gdk::Window> window = get_window();

    if (!window) {
        return true;
    }

    const double h(get_height());
    const double w(get_width());
    const double y(h - 2);
    const double ah(h - 4);
    const double aw(2 * ah);

    // Boundaries of the drawing area in time units.
    synfig::Time top(adj_timescale->get_upper());
    synfig::Time bottom(adj_timescale->get_lower());

    // The style of the widget.
    Glib::RefPtr<Gtk::StyleContext> style = get_style_context();
    Gtk::StateFlags state = style->get_state();

    // TODO hardcoded colors
    // Colors
    Color background(0.46, 0.55, 0.70, 1.0);
    Color normal(0.0, 0.0, 0.0, 1.0);
    Color selected(1.0, 1.0, 1.0, 1.0);
    Color drag_old_position(1.0, 1.0, 1.0, 0.6);
    Color drag_new_position(1.0, 1.0, 1.0, 1.0);

    // Draw a background
    cr->set_source_rgba(background.get_r(), background.get_g(), background.get_b(), background.get_a());
    cr->rectangle(0.0, 0.0, w, h);
    cr->fill();
    cr->restore();

    if (!editable_) {
        return true; // needs fixing!
    }

    // Returns if there are not keyframes to draw.
    if (kf_list_->empty()) {
        return false;
    }

    // Loop all the keyframes
    synfig::KeyframeList::iterator iter, selected_iter;
    bool show_selected(false);

    for (iter = kf_list_->begin(); iter != kf_list_->end(); iter++) {
        if (iter->get_time() > top || iter->get_time() < bottom) {
            continue;
        }

        if (*iter != selected_kf) {
            const double x = (double)(iter->get_time() - bottom) * (w / (top - bottom));
            draw_arrow(cr, x, y, aw, ah, iter->active(), normal);
        } else {
            selected_iter = iter;
            show_selected = true;
        }
    }

    // we do this so that we can be sure that
    // the selected keyframe is shown on top
    if (show_selected) {
        if (!dragging_) {
            const double x = (double)(selected_iter->get_time() - bottom) * (w / (top - bottom));
            draw_arrow(cr, x, y, aw, ah, selected_iter->active(), selected);
        } else {
            const double prev_x = (double)(selected_iter->get_time() - bottom) * (w / (top - bottom));
            const double new_x = (double)(dragging_kf_time - bottom) * (w / (top - bottom));
            draw_arrow(cr, prev_x, y, aw, ah, selected_iter->active(), drag_old_position);
            draw_arrow(cr, new_x, y, aw, ah, selected_iter->active(), drag_new_position);
        }
    }

    return true;
}

void
Widget_Keyframe_List::set_kf_list(synfig::KeyframeList* x)
{
    kf_list_ = x;
    set_selected_keyframe(selected_none);
    selected_ = false;
    dragging_ = false;
}

void
Widget_Keyframe_List::set_selected_keyframe(const synfig::Keyframe &x)
{
    if (x == selected_none) {
        return;
    }

    if (x == selected_kf) {
        // synfig::Keyframe::operator== only on uniqueid::operator==
        // \see synfig::UniqueID::operator==
        // In all case, refresh keyframe description to do not loose it
        selected_kf.set_description(x.get_description());
        // refresh keyframe time also.
        selected_kf.set_time(x.get_time());
        // and activation status
        selected_kf.set_active(x.active());
        return;
    }

    selected_kf = x;
    selected_ = true;
    dragging_kf_time = selected_kf.get_time();

    if (canvas_interface_) {
        canvas_interface_->signal_keyframe_selected()(selected_kf, (void*)this);
    }

    dragging_ = false;
    queue_draw();
}

void
Widget_Keyframe_List::on_keyframe_selected(synfig::Keyframe keyframe, void* emitter)
{
    if ((void*)this == emitter)	{
        return;
    }

    if (keyframe == selected_kf)	{
        return;
    }

    selected_kf = keyframe;
    selected_ = true;

    dragging_kf_time = selected_kf.get_time();
    dragging_ = false;
    queue_draw();
}

bool
Widget_Keyframe_List::perform_move_kf(bool delta = false)
{
    if (!selected_) {
        return false;
    }

    if (dragging_kf_time == selected_kf.get_time()) {
        return false;    // change this checking if not sticked to integer frames
    }

    Time selected_kf_time(selected_kf.get_time());
    Time prev, next;
    kf_list_->find_prev_next(selected_kf_time, prev, next);

    // Not possible to set delta to the first keyframe
    // perform normal movement
    // As suggested by Zelgadis it is better to not perform anything.
    if (prev == Time::begin() && delta == true) {
        synfig::info(_("Not possible to ALT-drag the first keyframe"));
        return false;
    }

    if (!delta) {
        synfigapp::Action::Handle action(synfigapp::Action::create("KeyframeSet"));

        if (!action) {
            return false;
        }

        selected_kf.set_time(dragging_kf_time);
        action->set_param("canvas", canvas_interface_->get_canvas());
        action->set_param("canvas_interface", canvas_interface_);
        action->set_param("keyframe", selected_kf);

        try {
            canvas_interface_->get_instance()->perform_action(action);
            canvas_interface_->signal_keyframe_selected()(selected_kf, (void*)this);
        } catch (...) {
            return false;
        }

    } else {
        // find prev from selected kf time including deactivated kf
        Keyframe prev_kf(*kf_list_->find_prev(selected_kf_time, false));
        Time prev_kf_time(prev_kf.get_time());

        if (prev_kf_time >= dragging_kf_time) { // Not allowed
            synfig::warning(_("Delta set not allowed"));
            synfig::info("Widget_Keyframe_List::perform_move_kf(%i)::prev_kf_time=%s", delta, prev_kf_time.get_string().c_str());
            synfig::info("Widget_Keyframe_List::perform_move_kf(%i)::dragging_kf_time=%s", delta, dragging_kf_time.get_string().c_str());
            return false;
        } else {
            Time old_delta_time(selected_kf_time - prev_kf_time);
            Time new_delta_time(dragging_kf_time - prev_kf_time);
            Time change_delta(new_delta_time - old_delta_time);
            synfigapp::Action::Handle action(synfigapp::Action::create("KeyframeSetDelta"));

            if (!action) {
                return false;
            }

            action->set_param("canvas", canvas_interface_->get_canvas());
            action->set_param("canvas_interface", canvas_interface_);
            action->set_param("keyframe", prev_kf);
            action->set_param("delta", change_delta);
            canvas_interface_->get_instance()->perform_action(action);
        }
    }

    queue_draw();
    return true;
}

bool
Widget_Keyframe_List::on_event(GdkEvent *event)
{
    // Do not respond mouse events if the list is empty
    if (!kf_list_->size()) {
        return true;
    }

    const int x(static_cast<int>(event->button.x));
    // Boundaries of the drawing area in time units.
    synfig::Time top(adj_timescale->get_upper());
    synfig::Time bottom(adj_timescale->get_lower());
    // pos is the [0,1] relative horizontal place on the widget
    float pos((float)x / (get_width()));

    if (pos < 0.0f) {
        pos = 0.0f;
    }

    if (pos > 1.0f) {
        pos = 1.0f;
    }

    // The time where the event x is
    synfig::Time t((float)(bottom + pos * (top - bottom)));

    // here the guts of the event
    switch (event->type) {
    case GDK_MOTION_NOTIFY:
        if (editable_) {
            // here is captured mouse motion
            // AND left or right mouse button pressed
            if (event->motion.state & (GDK_BUTTON1_MASK /*| GDK_BUTTON3_MASK*/)) {
                if (!selected_) {
                    return true;
                }

                // stick to integer frames. It can be optional in the future
                if (fps) {
                    t = floor(t * fps + 0.5) / fps;
                }

                dragging_kf_time = t;
                dragging_ = true;
                queue_draw();

                // Moving tooltip displaying the dragging time
                {
                    int x_root = static_cast<int>(event->button.x_root);
                    int x_origin;
                    int y_origin;
                    get_window()->get_origin(x_origin, y_origin);

                    Glib::ustring tooltip_label(_("Time : "));
                    tooltip_label.append(dragging_kf_time.get_string(fps, App::get_time_format()));
                    tooltip_label.append("\n");
                    tooltip_label.append(_("Old Time : "));
                    tooltip_label.append(selected_kf.get_time().get_string(fps, App::get_time_format()));
                    moving_tooltip_label_->set_text(tooltip_label);

                    if (!moving_tooltip_->get_visible()) {
                        // Show the tooltip and fix his y coordinate (up to the widget)
                        moving_tooltip_->show();
                        moving_tooltip_y_ = y_origin - moving_tooltip_->get_height();
                    }

                    // Move the tooltip to a nice position
                    moving_tooltip_->move(x_root, moving_tooltip_y_);
                }

                return true;
            }
            // here is captured mouse motion
            // AND NOT left or right mouse button pressed
            else {
                Glib::ustring ttip = "";
                synfig::Time p_t, n_t;
                kf_list_->find_prev_next(t, p_t, n_t);

                if ((p_t == Time::begin() 	&& 	n_t == Time::end())
                        ||
                        ((t - p_t) > time_ratio 	&& (n_t - t) > time_ratio)
                   ) {
                    ttip = _("Click and drag keyframes");
                } else if ((t - p_t) < (n_t - t)) {
                    synfig::Keyframe kf(*kf_list_->find_prev(t));
                    synfig::String kf_name(kf.get_description().c_str());
                    ttip = (kf_name.length() == 0) ? _("No name") : kf_name.c_str();
                } else {
                    synfig::Keyframe kf(*kf_list_->find_next(t));
                    synfig::String kf_name(kf.get_description().c_str());
                    ttip = (kf_name.length() == 0) ? _("No name") : kf_name.c_str();
                }

                set_tooltip_text(ttip);
                dragging_ = false;
                queue_draw();
                return true;
            }
        }

        break;

    case GDK_BUTTON_PRESS:
        changed_ = false;
        dragging_ = false;

        if (event->button.button == 1 || event->button.button == 3) {
            if (editable_) {
                synfig::Time prev_t, next_t;
                kf_list_->find_prev_next(t, prev_t, next_t, false);

                if ((prev_t == Time::begin() 	&& 	next_t == Time::end())
                        ||
                        ((t - prev_t) > time_ratio 	&& (next_t - t) > time_ratio)
                   ) {
                    switch (event->button.button) {
                    case 1:
                        set_selected_keyframe(selected_none);
                        selected_ = false;
                        queue_draw();
                        break;

                    case 3:
                        Gtk::Menu* menu = dynamic_cast<Gtk::Menu*>(App::ui_manager()->get_widget("/menu-keyframe"));

                        if (menu) {
                            menu->popup(event->button.button, gtk_get_current_event_time());
                        }

                        break;
                    }
                } else if ((t - prev_t) < (next_t - t)) {
                    switch (event->button.button) {
                    case 1:
                        set_selected_keyframe(*(kf_list_->find_prev(t, false)));
                        queue_draw();
                        selected_ = true;
                        break;

                    case 3:
                        set_selected_keyframe(*(kf_list_->find_prev(t, false)));
                        queue_draw();
                        selected_ = true;

                        Gtk::Menu* menu = dynamic_cast<Gtk::Menu*>(App::ui_manager()->get_widget("/menu-keyframe"));

                        if (menu) {
                            menu->popup(event->button.button, gtk_get_current_event_time());
                        }

                        break;
                    }
                } else {
                    switch (event->button.button) {
                    case 1:
                        set_selected_keyframe(*(kf_list_->find_next(t, false)));
                        queue_draw();
                        selected_ = true;
                        break;

                    case 3:
                        set_selected_keyframe(*(kf_list_->find_next(t, false)));
                        queue_draw();
                        selected_ = true;

                        Gtk::Menu* menu = dynamic_cast<Gtk::Menu*>(App::ui_manager()->get_widget("/menu-keyframe"));

                        if (menu) {
                            menu->popup(event->button.button, gtk_get_current_event_time());
                        }

                        break;
                    }
                }

                return true;
            } else {
                return false;
            }
        }

        break;

    case GDK_2BUTTON_PRESS:
        if (event->button.button == 1) {
            if (selected_ && editable_ && canvas_interface_) {
                canvas_interface_->signal_keyframe_properties()();
            }
        }

        break;

    case GDK_BUTTON_RELEASE:
        if (editable_ && (event->button.button == 1 /*|| event->button.button==3*/)) {
            // stick to integer frames.
            if (fps)	{
                t = floor(t * fps + 0.5) / fps;
            }

            bool stat = false;

            if (dragging_) {
                if (event->button.state & GDK_MOD1_MASK) {
                    stat = perform_move_kf(true);
                } else {
                    stat = perform_move_kf(false);
                }

                moving_tooltip_->hide();
            }

            dragging_ = false;
            return stat;
        }

        break;

    default:
        break;
    }

    return false;
}

void Widget_Keyframe_List::set_time_adjustment(const Glib::RefPtr<Gtk::Adjustment> &x)
{
    // disconnect old connections
    time_value_change.disconnect();
    time_other_change.disconnect();

    // connect update function to new adjustment
    adj_timescale = x;

    if (x) {
        time_value_change = x->signal_value_changed().connect(sigc::mem_fun(*this, &Widget_Keyframe_List::queue_draw));
        time_other_change = x->signal_changed().connect(sigc::mem_fun(*this, &Widget_Keyframe_List::queue_draw));
    }
}

void
Widget_Keyframe_List::set_fps(float d)
{
    if (fps != d) {
        fps = d;
        // update everything since we need to redraw already
        queue_draw();
    }
}

void
Widget_Keyframe_List::set_canvas_interface(etl::loose_handle<synfigapp::CanvasInterface> h)
{
    canvas_interface_ = h;

    // Store the values used from the canvas interface.
    if (canvas_interface_) {
        set_fps(canvas_interface_->get_canvas()->rend_desc().get_frame_rate());
        set_kf_list(&canvas_interface_->get_canvas()->keyframe_list());
        canvas_interface_->signal_keyframe_added().connect(
            sigc::hide_return(
                sigc::hide(
                    sigc::mem_fun(*this, &studio::Widget_Keyframe_List::queue_draw)
                )
            )
        );
        canvas_interface_->signal_keyframe_changed().connect(
            sigc::hide_return(
                sigc::hide(
                    sigc::mem_fun(*this, &studio::Widget_Keyframe_List::queue_draw)
                )
            )
        );
        canvas_interface_->signal_keyframe_removed().connect(
            sigc::hide_return(
                sigc::hide(
                    sigc::mem_fun(*this, &studio::Widget_Keyframe_List::queue_draw)
                )
            )
        );
        canvas_interface_->signal_keyframe_selected().connect(
            sigc::mem_fun(*this, &studio::Widget_Keyframe_List::on_keyframe_selected)
        );
    }
}